{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "![WoW](https://upload.wikimedia.org/wikipedia/ru/2/22/World_of_Warcraft_logo.png)\n",
    "\n",
    "Задача спрогнозировать победу игрока World Of Warcraft (режим Поле боя) по его данным за матч и выявить признаки, которые сильнее всего влияют на победу.\n",
    "\n",
    "Поле боя - это специальная территория для сражений между игроками противоположных фракций.Оказавшись внутри, две команды будут участвовать в масштабных сражениях, с различными целями (Захват флага, удерживание точек, сбор ресурсов и т.д). Игроки будут зарабатывать очки чести для покупки PvP брони и оружия.\n",
    "\n",
    "Зная оптимальные для победы признаки можно более осознано подходить к выбору персонажа и знать на какие стратегии нужно сильнее приложиться, чтобы выиграть матч.\n",
    "\n",
    "[Ссылка на данные](https://www.kaggle.com/cblesa/world-of-warcraft-battlegrounds)\n",
    "\n",
    "### Описание данных\n",
    "- Faction: Фракция (Орда или Альянс)\n",
    "- Class: Класс персонажа\n",
    "- KB: Количество смертельных убийств, данных игроком\n",
    "- D: Сколько раз игрок умер\n",
    "- HK: Количество убийств, в которых участвовал игрок или его группа.\n",
    "- DD: Урон, нанесенный игроком за всю игру\n",
    "- HD: Очки жизни, которые игрок востановил за всю игру\n",
    "- Honor: Очки чести, полученные игроком\n",
    "- Won: выиграла ли команда\n",
    "- Rol: Роль игрока \n",
    "    - dps если игрок наносил урон;\n",
    "    - heal, если игрок исцелял союзников\n",
    "- BE: в течение нескольких недель происходит бонусное событие, когда увеличивается вознаграждение. 1, если битва произошла на этой неделе."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import numpy as np\n",
    "import pandas as pd\n",
    "import seaborn as sns\n",
    "\n",
    "from sklearn.metrics import classification_report, confusion_matrix, roc_curve, recall_score\n",
    "from sklearn.preprocessing import LabelEncoder, OneHotEncoder, StandardScaler\n",
    "from sklearn.decomposition import PCA\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Загрузка данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "data = pd.read_csv('./data/wowbgs.csv')\n",
    "\n",
    "# преобразуем два бинарных признака в один бинарный\n",
    "data['Won'] = data['Win'] == 1.0\n",
    "del data['Win']\n",
    "del data['Lose']\n",
    "del data['Code']\n",
    "\n",
    "data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Преобразование признаков"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "for col in ['Battleground', 'Faction', 'Class', 'Rol', 'BE']:\n",
    "    enc = LabelEncoder()\n",
    "    data[col] = enc.fit_transform(data[col])\n",
    "data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Распределение результатов матчей\n",
    "plt.pie(data['Won'].value_counts(), labels=['Победа', 'Проигрыш'], autopct='%1.1f%%');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Добавим новый признак DamageRate, показывающий, \n",
    "# насколько сильно игрок помогал союзникам (учавствовал в командной игре)\n",
    "# чем он ближе к 1.0, тем сильнее игрок был вовлечен в командную игру\n",
    "data['DamageRate'] = data['HK'] / data['DD']\n",
    "data['DamageRate'][data['DamageRate'] == np.inf] = 0\n",
    "data['DamageRate'][data['DamageRate'].isnull()] = 0"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По графику видно, что классы уже сбалансированы."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## Визуальный анализ данных"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# посмотрим на корреляцию признаков\n",
    "sns.heatmap(data.corr());"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Можно заметить, что такие признаки связанные с нанесением урона довольно сильно коррелируют друг с другом.\n",
    "\n",
    "Итого, получаем, что с победой сильнее всего коррелируют следующие признаки:\n",
    "- Количество смертей\n",
    "- Очки чести\n",
    "- Количество убийств, в которых участвовал игрок\n",
    "\n",
    "Также, по логике, на победу должно влиять количество урона, нанесенное игроком."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "rol_data = data[['Rol', 'DD', 'Won']]\n",
    "rol_data['Rol'] = rol_data['Rol'].map({0: \"Damage\", 1: \"Heal\"})\n",
    "sns.factorplot(x=\"Rol\", y='DD', hue='Won', data=rol_data, size=6, kind=\"bar\", palette=\"muted\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По распределению наносимого урона между разными ролями видно, что игроки, которые занимаются исцелением союзников наносят куда меньше урона, чем остальные. \n",
    "\n",
    "Плюс игроки, которые выигрывают матчи, наносят больше урона, чем проигравшие.\n",
    "<img src=\"https://memegenerator.net/img/instances/69532381/thank-you-captain-obvious.jpg\" alt=\"Obvious\" style=\"width: 300px;\"/>\n",
    "\n",
    "Отобразим парное распределение признаков, коррелирующих с целевой переменной."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "sns.pairplot(data[['Honor', 'HK', 'DD', 'Won']], hue=\"Won\");"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "По этим графикам можно увидеть, что нужный класс хорошо выделяется.\n",
    "\n",
    "Из этого можно сделать вывод, что для алгоритмов машинного обучения данная задача не будет сложной."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# проверяем наличие пропусков\n",
    "for col in data.columns:\n",
    "    print(col, data[col].isnull().sum())"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "from sklearn.model_selection import GridSearchCV, StratifiedKFold\n",
    "from sklearn.ensemble import RandomForestClassifier\n",
    "\n",
    "cv = StratifiedKFold(n_splits=4)\n",
    "\n",
    "X = data[list(set(data.columns) - set(['Won']))]\n",
    "y = data['Won']\n",
    "\n",
    "# На графиках видно, что искомый класс не сложно отделить.\n",
    "# Попробуем усложнить задачу, взяв в качестве отложенной выборки половину всех данных\n",
    "X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.5, random_state=17, shuffle=True)\n",
    "\n",
    "# Масштабируем признаки\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(X_train)\n",
    "X_train = scaler.transform(X_train)\n",
    "X_test = scaler.transform(X_test)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Будем использовать случайный лес, тк с его помощью мы сможем легко выяснить какие признаки привнесли наибольший вклад.\n",
    "\n",
    "В качестве метрики будем использовать `recall` (True Positive Rate): \n",
    "\n",
    "${\\mathit {TPR}}=\\frac{\\mathit {TP}}{P}=\\frac{{\\mathit {TP}}}{({\\mathit {TP}}+{\\mathit {FN}})}$, тк мы решаем задачу бинарной классификации и нам наиболее важно научиться определять победу игрока."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "forest = RandomForestClassifier(random_state=17)\n",
    "forest_params = {\n",
    "    'n_estimators': list(range(40, 50)),\n",
    "    'max_depth': list(range(10, 15)),\n",
    "}\n",
    "forest_grid = GridSearchCV(forest, forest_params, scoring='recall', cv=cv)\n",
    "forest_grid.fit(X_train, y_train)\n",
    "clf = forest_grid.best_estimator_"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Кривые обучения и валидации"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.model_selection import learning_curve\n",
    "\n",
    "plt.figure()\n",
    "train_sizes, train_scores, test_scores = learning_curve(\n",
    "    clf, X, y, n_jobs=-1)\n",
    "\n",
    "train_scores_mean = np.mean(train_scores, axis=1)\n",
    "train_scores_std = np.std(train_scores, axis=1)\n",
    "test_scores_mean = np.mean(test_scores, axis=1)\n",
    "test_scores_std = np.std(test_scores, axis=1)\n",
    "plt.grid()\n",
    "\n",
    "plt.xlabel(\"Число примеров\")\n",
    "plt.ylabel(\"Метрика\")\n",
    "plt.fill_between(train_sizes, train_scores_mean - train_scores_std,\n",
    "                 train_scores_mean + train_scores_std, alpha=0.1,\n",
    "                 color=\"r\")\n",
    "plt.fill_between(train_sizes, test_scores_mean - test_scores_std,\n",
    "                 test_scores_mean + test_scores_std, alpha=0.1, color=\"g\")\n",
    "plt.plot(train_sizes, train_scores_mean, 'o-', color=\"r\",\n",
    "         label=\"Кривая обучения\")\n",
    "plt.plot(train_sizes, test_scores_mean, 'o-', color=\"g\",\n",
    "         label=\"Кривая валидации\")\n",
    "\n",
    "plt.legend(loc=\"best\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Проверка модели на отложенной выборке"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "y_test_predict = clf.predict(X_test)\n",
    "\n",
    "# Посмотрим на качество модели на отложенной выборке\n",
    "print('recall_score:', round(clf.score(X_test, y_test), 4))\n",
    "\n",
    "print(classification_report(y_test, y_test_predict))\n",
    "\n",
    "cm = confusion_matrix(y_test, y_test_predict);\n",
    "labels = ['Won', 'Lose']\n",
    "sns.heatmap(pd.DataFrame(cm, columns=labels, index=labels), annot=True, fmt='d');"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "print('recall на отложенной выборке:', recall_score(y_test, y_test_predict))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fpr_rf_lm, tpr_rf_lm, _ = roc_curve(y_test, y_test_predict)\n",
    "\n",
    "plt.plot([0, 1], [0, 1], 'k--');\n",
    "plt.plot(fpr_rf_lm, tpr_rf_lm);\n",
    "plt.xlabel('False positive rate');\n",
    "plt.ylabel('True positive rate');\n",
    "plt.title('ROC curve');"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Определение вклада каждого признака"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "feature_importances = []\n",
    "for i, col in enumerate(list(set(data.columns) - set(['Won']))):\n",
    "    feature_importances.append(round(clf.feature_importances_[i], 4))\n",
    "pd.DataFrame(\n",
    "    feature_importances,\n",
    "    columns=['Importance'],\n",
    "    index=list(set(data.columns) - set(['Won']))\n",
    ").sort_values(by=['Importance'],ascending=False)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "\n",
    "## Выводы\n",
    "\n",
    "Построена модель бинарного классификатора, предсказывающего результат матча в игре World Of Warcraft.\n",
    "На отложенной выборке получили долю правильных предсказаний 0.98%, что является очень хорошим показателем.\n",
    "\n",
    "Получаем, чтобы победить в групповом сражении в MMO игре, стоит делать больший упор не на увеличение наносимого урона, а на коммандную игру (помогать союзникам наносить урон и зарабатывать очки чести).\n",
    "\n",
    "<img src=\"https://301-1.ru/img_files/gifs/2014_12_23_ea801005ae2a8b2ad07f850629b6639c.gif\" alt=\"\" style=\"width: 300px;\"/>\n",
    "\n",
    "Также с помощью построенной модели можно по статистике игрока предсказывать результат сражения."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.6.3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
